/**
 * P6Spy
 *
 * Copyright (C) 2002 - 2020 P6Spy
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.swak.persistence.metrics.wrapper;

import java.sql.SQLException;
import java.sql.Wrapper;

import com.swak.meters.MetricsFactory;
import com.swak.meters.SqlMetrics;

public abstract class AbstractWrapper implements Wrapper, P6Proxy {

	private final Object delegate;
	private final MetricsFactory metricsFactory;

	protected AbstractWrapper(Object delegate, MetricsFactory metricsFactory) {
		this.delegate = delegate;
		this.metricsFactory = metricsFactory;
	}

	/**
	 * Used to determine is a given object is a Proxy created by this proxy factory.
	 *
	 * @param obj the object in question
	 * @return true if it is a proxy - false otherwise
	 */
	public static boolean isProxy(final Object obj) {
		return (obj != null && isProxy(obj.getClass()));
	}

	/**
	 * Used to determine if the given class is a proxy class.
	 *
	 * @param clazz the class in question
	 * @return true if proxy - false otherwise
	 */
	public static boolean isProxy(final Class<?> clazz) {
		return (clazz != null && P6Proxy.class.isAssignableFrom(clazz));
	}

	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		final Object result;
		if (iface.isAssignableFrom(getClass())) {
			// if the proxy directly implements the interface or extends it, return the
			// proxy
			result = this;
		} else if (iface.isAssignableFrom(delegate.getClass())) {
			// if the proxied object directly implements the interface or extends it, return
			// the proxied object
			result = unwrapP6SpyProxy();
		} else if (Wrapper.class.isAssignableFrom(delegate.getClass())) {
			// if the proxied object implements the wrapper interface, then
			// return the result of it's unwrap method.
			result = ((Wrapper) unwrapP6SpyProxy()).unwrap(iface);
		} else {
			/*
			 * This line of code can only be reached when the underlying object does not
			 * implement the wrapper interface. This would mean that either the JDBC driver
			 * or the wrapper of the underlying object does not implement the JDBC 4.0 API.
			 */
			throw new SQLException("Can not unwrap to " + iface.getName());
		}
		return iface.cast(result);
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		if (iface.isAssignableFrom(getClass())) {
			// if the proxy directly proxy the interface or extends it, return true
			return true;
		} else if (iface.isAssignableFrom(delegate.getClass())) {
			// if the proxied object directly implements the interface or extends it, return
			// true
			return true;
		} else if (Wrapper.class.isAssignableFrom(delegate.getClass())) {
			// if the proxied object implements the wrapper interface, then
			// return the result of it's isWrapperFor method.
			return ((Wrapper) unwrapP6SpyProxy()).isWrapperFor(iface);
		}
		return false;
	}

	@Override
	public boolean equals(Object obj) {
		// If the object to pass to the equals method is another P6Spy proxy, then
		// unwrap it first.
		if (obj instanceof P6Proxy) {
			obj = ((P6Proxy) obj).unwrapP6SpyProxy();
		}
		return delegate.equals(obj);
	}

	@Override
	public int hashCode() {
		return delegate.hashCode();
	}

	@Override
	public Object unwrapP6SpyProxy() {
		return delegate;
	}

	@SuppressWarnings({ "unchecked" })
	protected SqlMetrics<Object> beginMetrics(String sql) {
		return this.metricsFactory != null ? (SqlMetrics<Object>) this.metricsFactory.createSqlMetrics(sql) : null;
	}
}
